# -*- coding: utf-8 -*-

import importlib
import sys
import time
from collections import deque
from datetime import datetime
from functools import partial
import datetime as dt

# 其他
import numpy as np
import pandas as pd
from PyQt6 import QtCore
from PyQt6.QtCore import Qt
import pyqtgraph as pg
from PyQt6 import QtWidgets, QtGui
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QApplication
from pyqtgraph.Point import Point
from vnpy.chart.const import *

importlib.reload(sys)
# sys.setdefaultencoding('utf8')
# 字符串转换
# ---------------------------------------------------------------------------------------
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

# 每日最多Tick数，每秒2根，白天4小时+夜盘3+2.5小时
MAX_TICKS = 2 * 60 * 60 * (4 + 5.5)


########################################################################
# 十字光标支持
########################################################################
class Crosshair(QtCore.QObject):
    """
    此类给pg.PlotWidget()添加crossHair功能,PlotWidget实例需要初始化时传入
    """
    signal = QtCore.pyqtSignal(type(tuple([])))

    # ----------------------------------------------------------------------
    def __init__(self, parent, master, sync_num=3):
        """Constructor"""
        self.__view = parent
        self.master = master
        super(Crosshair, self).__init__()

        self.xAxis = 0
        self.yAxis = 0
        # 需要共用十字光标的绘图区域数
        self.sync_num = sync_num
        self.visible = True
        self.data = None

        self.yAxises = [0 for i in range(self.sync_num)]
        self.leftX = [0 for i in range(self.sync_num)]
        self.showHLine = [False for i in range(self.sync_num)]
        self.textPrices = [pg.TextItem('', anchor=(1, 1)) for i in range(self.sync_num)]
        self.views = [parent.centralWidget.getItem(i + 1, 0) for i in range(self.sync_num)]
        self.rects = [self.views[i].sceneBoundingRect() for i in range(self.sync_num)]
        self.vLines = [pg.InfiniteLine(angle=90, movable=False) for i in range(self.sync_num)]
        self.hLines = [pg.InfiniteLine(angle=0, movable=False) for i in range(self.sync_num)]
        # 保存当前位置
        self.saved_vpos = None
        self.saved_hpos = None
        # mid 在y轴动态跟随最新价显示最新价和最新时间
        self.__textData = pg.TextItem('dataInfo')
        self.__textDate = pg.TextItem('date')
        self.__textInfo = pg.TextItem('lastBarInfo')
        self.__textSig = pg.TextItem('lastSigInfo', anchor=(1, 0))
        self.__textIndicators = pg.TextItem('SAR', anchor=(1, 0))
        self.__textVolume = pg.TextItem('lastBarVolume', anchor=(1, 0))

        self.__textDate.setZValue(2)
        self.__textInfo.setZValue(2)
        self.__textSig.setZValue(2)
        self.__textData.setZValue(2)
        self.__textVolume.setZValue(2)
        self.__textInfo.border = pg.mkPen(color=(230, 255, 0, 255), width=1.2)
        self.__textIndicators.setZValue(2)  # 指标
        self.__textData.border = pg.mkPen(color=(230, 255, 0, 255), width=1.2)

        for i in range(self.sync_num):
            self.textPrices[i].setZValue(2)
            self.vLines[i].setPos(0)
            self.hLines[i].setPos(0)
            self.views[i].addItem(self.vLines[i])
            self.views[i].addItem(self.hLines[i])
            self.views[i].addItem(self.textPrices[i])

        self.views[0].addItem(self.__textInfo, ignoreBounds=True)
        self.views[0].addItem(self.__textSig, ignoreBounds=True)
        self.views[0].addItem(self.__textIndicators, ignoreBounds=True)
        # self.views[1].addItem(self.__textVolume, ignoreBounds=True)
        # self.views[2].addItem(self.__textDate, ignoreBounds=True)

        self.proxy = pg.SignalProxy(self.__view.scene().sigMouseMoved, rateLimit=60, slot=self.__mouseMoved)
        # 跨线程刷新界面支持
        self.signal.connect(self.update)

    def set_sync_num(self, sync_num):
        self.sync_num = sync_num

    # ----------------------------------------------------------------------
    def update(self, pos):
        """刷新界面显示"""
        xAxis, yAxis = pos
        xAxis, yAxis = (self.xAxis, self.yAxis) if xAxis is None else (xAxis, yAxis)
        if self.data is None:
            return
        self.moveTo(xAxis, yAxis)

    # ----------------------------------------------------------------------
    def __mouseMoved(self, evt):
        """鼠标移动回调"""
        if self.data is None:
            return
        if not self.visible:
            return
        # print('before __mouseMoved')
        pos = evt[0]
        self.rects = [self.views[i].sceneBoundingRect() for i in range(self.sync_num)]

        for i in range(self.sync_num):
            self.showHLine[i] = False
            if self.rects[i].contains(pos):
                mousePoint = self.views[i].vb.mapSceneToView(pos)
                xAxis = mousePoint.x()
                yAxis = mousePoint.y()
                self.yAxises[i] = yAxis
                self.showHLine[i] = True
                self.moveTo(xAxis, yAxis)

    # ----------------------------------------------------------------------
    def moveTo(self, xAxis, yAxis):
        # print('before moveTo')
        xAxis, yAxis = (self.xAxis, self.yAxis) if xAxis is None else (xAxis, yAxis)
        self.rects = [self.views[i].sceneBoundingRect() for i in range(self.sync_num)]

        if not xAxis or not yAxis:
            return
        self.xAxis = xAxis
        self.yAxis = yAxis
        self.vhLinesSetXY(xAxis, yAxis)
        self.plotPrice(yAxis)
        self.plotDataInfo(xAxis)
        # if self.master.main_graph_type == GRAPH_TYPE_KBAR:
        #     self.plotInfo(xAxis)
        # else:
        #     self.plotDataInfo(xAxis)
        # print('after moveTo')

        # ----------------------------------------------------------------------

    def vhLinesSetXY(self, xAxis, yAxis):
        """水平和竖线位置设置"""
        for i in range(self.sync_num):
            self.vLines[i].setPos(xAxis)
            if self.showHLine[i]:
                self.hLines[i].setPos(yAxis if i == 0 else self.yAxises[i])
            else:
                topLeft = self.views[i].vb.mapSceneToView(QtCore.QPointF(self.rects[i].left(), self.rects[i].top()))
                self.hLines[i].setPos(topLeft.y() + abs(topLeft.y()))

    # ----------------------------------------------------------------------
    def plotPrice(self, yAxis):
        """价格位置设置"""
        for i in range(self.sync_num):
            if self.showHLine[i]:
                rightAxis = self.views[i].getAxis('right')
                rightAxisWidth = rightAxis.width()
                topRight = self.views[i].vb.mapSceneToView(
                    QtCore.QPointF(self.rects[i].right() - rightAxisWidth, self.rects[i].top()))
                self.textPrices[i].setHtml(
                    '<div style="text-align: right;">\
                        <span style="color: yellow; font-size: 12px;">\
                          %0.3f\
                        </span>\
                    </div>' \
                    % (yAxis if i == 0 else self.yAxises[i]))
                self.textPrices[i].setPos(topRight.x(), yAxis if i == 0 else self.yAxises[i])
            else:
                topRight = self.views[i].vb.mapSceneToView(QtCore.QPointF(self.rects[i].right(), self.rects[i].top()))
                self.textPrices[i].setPos(topRight.x(), topRight.y() + abs(topRight.y()))

    # ----------------------------------------------------------------------
    def plotInfo(self, xAxis):
        """
        被嵌入的plotWidget在需要的时候通过调用此方法显示K线信息
        """
        xAxis = round(xAxis)
        # print('xAxis')
        if self.data is None:
            return
        try:
            # 获取K线数据
            tickDatetime = self.data[int(xAxis)]['datetime']
            openPrice = self.data[int(xAxis)]['open']
            closePrice = self.data[int(xAxis)]['close']
            lowPrice = self.data[int(xAxis)]['low']
            highPrice = self.data[int(xAxis)]['high']
            volume = self.data[int(xAxis)]['volume']
            # openInterest = self.data[int(xAxis)]['openInterest']
            preClosePrice = self.data[int(xAxis) - 1]['close']
        except Exception as e:
            # print('error:{} in plotInfo'.format(e))
            return

        if isinstance(tickDatetime, np.datetime64):
            tickDatetime = tickDatetime.astype(str)
            tickDatetime = tickDatetime[:-4]

        if isinstance(tickDatetime, (dt.datetime)):
            datetimeText = dt.datetime.strftime(tickDatetime, '%Y-%m-%d %H:%M:%S')
            dateText = dt.datetime.strftime(tickDatetime, '%Y-%m-%d')
            timeText = dt.datetime.strftime(tickDatetime, '%H:%M:%S')
        elif (isinstance(tickDatetime, (str))):
            datetimeText = tickDatetime
            dateTemp = dt.datetime.strptime(datetimeText, '%Y-%m-%dT%H:%M:%S.%f')
            dateText = dt.datetime.strftime(dateTemp, '%Y-%m-%d')
            timeText = dt.datetime.strftime(dateTemp, '%H:%M:%S')
        else:
            datetimeText = ""
            dateText = ""
            timeText = ""

        # 显示所有的主图技术指标
        html = u'<div style="text-align: right">'
        for sig in self.master.sigData:
            val = self.master.sigData[sig][int(xAxis)]
            col = self.master.sigColor[sig]
            html += u'<span style="color: %s;  font-size: 12px;">&nbsp;&nbsp;%s：%.2f</span>' % (col, sig, val)
        html += u'</div>'
        self.__textSig.setHtml(html)

        # 显示指标
        # if self.master.sarDatas is not None:
        #     # arrow=self.master.sars[int(xAxis)]
        #     self.__textIndicators.setHtml(
        #         '<div style="text-align: center">\
        #             <span style="color: yellow; font-size: 12px;">SAR:%.2f</span>\
        #         </div>' \
        #         % (self.master.sarDatas[int(xAxis)]))

        # 和上一个收盘价比较，决定K线信息的字符颜色
        openText = "%.3f" % openPrice
        closeText = "%.3f" % closePrice
        highText = "%.3f" % highPrice
        lowText = "%.3f" % lowPrice
        cOpen = 'red' if openPrice > preClosePrice else 'green'
        cClose = 'red' if closePrice > preClosePrice else 'green'
        cHigh = 'red' if highPrice > preClosePrice else 'green'
        cLow = 'red' if lowPrice > preClosePrice else 'green'

        self.__textInfo.setHtml(
            """<div style="text-align: center; background-color:#000;height:auto ">
                <span style="color: white;  font-size: 12px;">日期</span><br>
                <span style="color: yellow; font-size: 12px;">%s</span><br>
                <span style="color: white;  font-size: 12px;">时间</span><br>
                <span style="color: yellow; font-size: 12px;">%s</span><br>
                <span style="color: white;  font-size: 12px;">开盘</span><br>
                <span style="color: %s;     font-size: 12px;">%s</span><br>
                <span style="color: white;  font-size: 12px;">最高</span><br>
                <span style="color: %s;     font-size: 12px;">%s</span><br>
                <span style="color: white;  font-size: 12px;">最低</span><br>
                <span style="color: %s;     font-size: 12px;">%s</span><br>
                <span style="color: white;  font-size: 12px;">收盘</span><br>
                <span style="color: %s;     font-size: 12px;">%s</span><br>
                <span style="color: white;  font-size: 12px;">成交量</span><br>
                <span style="color: yellow; font-size: 12px;">%.3f</span><br>
            </div>""" % (dateText, timeText, cOpen, openText, cHigh, highText,
                         cLow, lowText, cClose, closeText, volume))
        # print(self.__textInfo)
        self.__textDate.setHtml(
            '<div style="text-align: center">\
                <span style="color: yellow; font-size: 12px;">%s</span>\
            </div>' \
            % (datetimeText))

        self.__textVolume.setHtml(
            '<div style="text-align: right">\
                <span style="color: white; font-size: 12px;">VOL : %.3f</span>\
            </div>' \
            % (volume))

        # K线子图，左上角显示
        leftAxis = self.views[0].getAxis('left')
        leftAxisWidth = leftAxis.width()
        topLeft = self.views[0].vb.mapSceneToView(
            QtCore.QPointF(self.rects[0].left() + leftAxisWidth, self.rects[0].top()))
        x = topLeft.x()
        y = topLeft.y()
        # print(x,y)
        self.__textInfo.setPos(x, y)

        # K线子图，左上角显示
        leftAxis = self.views[0].getAxis('left')
        leftAxisWidth = leftAxis.width()
        topLeft = self.views[0].vb.mapSceneToView(
            QtCore.QPointF(self.rects[0].left() + leftAxisWidth, self.rects[0].top()))
        x = topLeft.x()
        y = topLeft.y()
        self.__textSig.setPos(x, y)

        # K线子图，右上角显示
        rightAxis = self.views[0].getAxis('right')
        rightAxisWidth = rightAxis.width()
        topRight = self.views[0].vb.mapSceneToView(
            QtCore.QPointF(self.rects[0].right() - rightAxisWidth, self.rects[0].top()))
        x = topRight.x()
        y = topRight.y()
        self.__textIndicators.setPos(x, y)

        # 成交量子图，右上角显示
        rightAxis = self.views[1].getAxis('right')
        rightAxisWidth = rightAxis.width()
        topRight = self.views[1].vb.mapSceneToView(
            QtCore.QPointF(self.rects[1].right() - rightAxisWidth, self.rects[1].top()))
        x = topRight.x()
        y = topRight.y()
        self.__textVolume.setPos(x, y)

        # X坐标时间显示
        rectTextDate = self.__textDate.boundingRect()
        rectTextDateHeight = rectTextDate.height()
        bottomAxis = self.views[2].getAxis('bottom')
        bottomAxisHeight = bottomAxis.height()
        bottomRight = self.views[2].vb.mapSceneToView(QtCore.QPointF(self.rects[2].width(),
                                                                     self.rects[2].bottom() - (
                                                                             bottomAxisHeight + rectTextDateHeight)))
        # 修改对称方式防止遮挡
        if xAxis > self.master.x_center:
            self.__textDate.anchor = Point((1, 0))
        else:
            self.__textDate.anchor = Point((0, 0))
        self.__textDate.setPos(xAxis, bottomRight.y())

    def plotDataInfo(self, xAxis):
        """
        被嵌入的plotWidget在需要的时候通过调用此方法显示K线信息
        """
        row_no = int(round(xAxis))
        # print(row_no)
        if self.data is None:
            return
        if not self.master or not self.master.fields_name:
            return
        fields_name = self.master.fields_name
        if row_no < 0:
            row_no = 0
        if row_no > len(self.data) - 1:
            row_no = -1

        info = '<div style="text-align: center; background-color:#000;height:auto ">'
        # if dateText and timeText:
        #     info += """<span style="color: white;  font-size: 12px;">日期:</span>
        #         <span style="color: yellow; font-size: 12px;">{}</span>
        #         <span style="color: white;  font-size: 12px;">时间:</span>
        #         <span style="color: yellow; font-size: 12px;">{}</span><br>""".format(dateText, timeText)
        name_value = ''
        for fn in fields_name:
            if fn != 'datetime':
                name_value += """<span style="color: white;  font-size: 12px;">{}:</span>
                <span style="color: yellow; font-size: 12px;">{}</span><br>""".format(fn, self.data[row_no][fn])
        info += name_value
        info += '</div>'
        # print('name_value',name_value)
        self.__textInfo.setHtml(info)
        # 主图，左上角显示
        leftAxis = self.views[0].getAxis('left')
        leftAxisWidth = leftAxis.width()
        topLeft = self.views[0].vb.mapSceneToView(
            QtCore.QPointF(self.rects[0].left() + leftAxisWidth, self.rects[0].top()))
        x = topLeft.x()
        y = topLeft.y()
        # print(x, y)
        self.__textInfo.setPos(x + 1, y)

        # self.__textDate.setHtml(
        #     '<div style="text-align: center">\
        #         <span style="color: yellow; font-size: 12px;">%s</span>\
        #     </div>' \
        #     % (datetimeText))
        # X坐标时间显示
        rectTextDate = self.__textDate.boundingRect()
        rectTextDateHeight = rectTextDate.height()
        bottomAxis = self.views[0].getAxis('bottom')
        bottomAxisHeight = bottomAxis.height()
        bottomRight = self.views[0].vb.mapSceneToView(QtCore.QPointF(self.rects[0].width(),
                                                                     self.rects[0].bottom() - (
                                                                             bottomAxisHeight + rectTextDateHeight)))
        # 修改对称方式防止遮挡
        if row_no > self.master.x_center:
            self.__textDate.anchor = Point((1, 0))
        else:
            self.__textDate.anchor = Point((0, 0))
        self.__textDate.setPos(row_no, bottomRight.y())

    def show(self):
        self.visible = True
        if self.saved_hpos:
            for i, line in enumerate(self.hLines):
                line.setValue(self.saved_hpos[i])
        if self.saved_vpos:
            for i, line in enumerate(self.vLines):
                line.setValue(self.saved_vpos[i])

    def hide(self):
        self.visible = False
        self.saved_hpos = [line.value() for line in self.hLines]
        self.saved_vpos = [line.value() for line in self.vLines]
        self.__textInfo.setHtml('')
        self.__textData.setHtml('')
        if self.saved_hpos:
            for i, line in enumerate(self.hLines):
                line.setValue(0)
        if self.saved_vpos:
            for i, line in enumerate(self.vLines):
                line.setValue(0)


########################################################################
# 键盘鼠标功能
########################################################################
class KeyWraper(QtWidgets.QWidget):
    """键盘鼠标功能支持的元类"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)

    # 重载方法keyPressEvent(self,event),即按键按下事件方法
    # ----------------------------------------------------------------------
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Up:
            self.onUp()
        elif event.key() == QtCore.Qt.Key_Down:
            self.onDown()
        elif event.key() == QtCore.Qt.Key_Left:
            self.onLeft()
        elif event.key() == QtCore.Qt.Key_Right:
            self.onRight()
        elif event.key() == QtCore.Qt.Key_PageUp:
            self.onPre()
        elif event.key() == QtCore.Qt.Key_PageDown:
            self.onNxt()

    # 重载方法mousePressEvent(self,event),即鼠标点击事件方法
    # ----------------------------------------------------------------------
    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.RightButton:
            self.onRClick(event.pos())
        elif event.button() == QtCore.Qt.LeftButton:
            self.onLClick(event.pos())

        event.accept()

    # 重载方法mouseReleaseEvent(self,event),即鼠标点击事件方法
    # ----------------------------------------------------------------------
    def mouseRelease(self, event):
        if event.button() == QtCore.Qt.RightButton:
            self.onRRelease(event.pos())
        elif event.button() == QtCore.Qt.LeftButton:
            self.onLRelease(event.pos())
        self.releaseMouse()

    def mouseDoubleClickEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.onLDoubleClick(event.pos())

    # 重载方法wheelEvent(self,event),即滚轮事件方法
    # ----------------------------------------------------------------------
    def wheelEvent(self, event):
        try:
            if event.delta() > 0:
                self.onUp()
            else:
                self.onDown()
        except:
            if event.angleDelta().y() / 120.0 > 0:
                self.onUp()
            else:
                self.onDown()

    # 重载方法dragMoveEvent(self,event),即拖动事件方法
    # ----------------------------------------------------------------------
    def paintEvent(self, event):
        self.onPaint()

    # PgDown键
    # ----------------------------------------------------------------------
    def onNxt(self):
        pass

    # PgUp键
    # ----------------------------------------------------------------------
    def onPre(self):
        pass

    # 向上键和滚轮向上
    # ----------------------------------------------------------------------
    def onUp(self):
        pass

    # 向下键和滚轮向下
    # ----------------------------------------------------------------------
    def onDown(self):
        pass

    # 向左键
    # ----------------------------------------------------------------------
    def onLeft(self):
        pass

    # 向右键
    # ----------------------------------------------------------------------
    def onRight(self):
        pass

    # 鼠标左单击
    # ----------------------------------------------------------------------
    def onLClick(self, pos):
        pass

    # 鼠标右单击
    # ----------------------------------------------------------------------
    def onRClick(self, pos):
        pass

    def onLDoubleClick(self, pos):
        """鼠标左双击"""
        pass

    # 鼠标左释放
    # ----------------------------------------------------------------------
    def onLRelease(self, pos):
        pass

    # 鼠标右释放
    # ----------------------------------------------------------------------
    def onRRelease(self, pos):
        pass

    # 画图
    # ----------------------------------------------------------------------
    def onPaint(self):
        pass


########################################################################
# 选择缩放功能支持
########################################################################
class CustomViewBox(pg.ViewBox):
    # ----------------------------------------------------------------------
    def __init__(self, *args, **kwds):
        pg.ViewBox.__init__(self, *args, **kwds)
        # 拖动放大模式
        # self.setMouseMode(self.RectMode)

    # 右键自适应
    # ----------------------------------------------------------------------
    def mouseClickEvent(self, ev):
        if ev.button() == QtCore.Qt.RightButton:
            self.autoRange()


########################################################################
# 时间序列，横坐标支持
########################################################################
class StringAxisX(pg.AxisItem):
    """时间序列横坐标支持"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, xdict, *args, **kwargs):
        pg.AxisItem.__init__(self, *args, **kwargs)
        self.minVal = 0
        self.maxVal = 100
        self.xdict = xdict
        self.x_values = np.asarray(list(xdict.keys()))  # np.asarray(xdict.keys())
        self.x_strings = np.asarray(list(self.xdict.values()))
        self.setPen(color=(255, 255, 255, 255), width=0.8)
        self.setStyle(tickFont=QFont("Roman times", 10, QFont.Bold), autoExpandTextSpace=True)
        # self.scale = 1

    # 更新坐标映射表
    # ----------------------------------------------------------------------
    def update_xdict(self, xdict):
        self.xdict.update(xdict)
        self.x_values = np.asarray(list(xdict.keys()))  # np.asarray(xdict.keys())
        self.x_strings = np.asarray(list(self.xdict.values()))
        # print('scale=, spacing={}, self.scale={}'.format('super()._tickSpacing', self.scale))

    # 将原始横坐标转换为时间字符串,第一个坐标包含日期
    # ----------------------------------------------------------------------
    def tickStrings(self, values, scale, spacing):
        strings = []

        for v in values:
            vs = int(v * scale)
            if vs in self.x_values:
                # np.array.argmin() 返回最小值的位置（从0开始）
                vstr = self.x_strings[np.abs(self.x_values - vs).argmin()]
                if not isinstance(vstr, (str, int, float)):
                    vstr = vstr.strftime('%Y-%m-%d %H:%M:%S')
            else:
                vstr = ""
            strings.append(vstr)
        # print( 'scale={}, spacing={}, self.scale={}'.format( scale, spacing, self.scale))
        return strings


########################################################################
# K线图形对象
########################################################################
class CandlestickItem(pg.GraphicsObject):
    """K线图形对象"""

    # 初始化
    # ----------------------------------------------------------------------
    def __init__(self, data):
        """初始化"""
        pg.GraphicsObject.__init__(self)
        # 数据格式: [ (time, open, close, low, high),...]
        self.data = data
        # 只重画部分图形，大大提高界面更新速度
        self.setFlag(self.ItemUsesExtendedStyleOption)
        # 画笔和画刷
        w = 0.4
        self.offset = 0
        self.low = 0
        self.high = 1
        self.picture = QtGui.QPicture()
        self.pictures = []
        self.bPen = pg.mkPen(color=(0, 240, 240, 255), width=w * 2)
        self.bBrush = pg.mkBrush((0, 240, 240, 255))
        self.rPen = pg.mkPen(color=(255, 60, 60, 255), width=w * 2)
        self.rBrush = pg.mkBrush((255, 60, 60, 255))
        self.rBrush.setStyle(Qt.NoBrush)
        # 刷新K线
        self.generatePicture(self.data)

        # 画K线

    # ----------------------------------------------------------------------
    def generatePicture(self, data=None, redraw=False):
        """重新生成图形对象"""
        # 重画或者只更新最后一个K线
        if data is None:
            return
        # print(type(data))
        if redraw:
            self.pictures = []
        elif self.pictures:
            self.pictures.pop()
        w = 0.4
        bPen = self.bPen
        bBrush = self.bBrush
        rPen = self.rPen
        rBrush = self.rBrush
        low, high = (data[0]['low'], data[0]['high']) if len(data) > 0 else (0, 1)
        # print(data)
        for (t, open0, close0, low0, high0) in data:
            if t >= len(self.pictures):
                low, high = (min(low, low0), max(high, high0))
                picture = QtGui.QPicture()
                p = QtGui.QPainter(picture)
                # 下跌蓝色（实心）, 上涨红色（空心）
                pen, brush, pmin, pmax = (bPen, bBrush, close0, open0) \
                    if open0 > close0 else (rPen, rBrush, open0, close0)
                p.setPen(pen)
                p.setBrush(brush)
                # 画K线方块和上下影线
                if open0 == close0:
                    p.drawLine(QtCore.QPointF(t - w, open0), QtCore.QPointF(t + w, close0))
                else:
                    p.drawRect(QtCore.QRectF(t - w, open0, w * 2, close0 - open0))
                if pmin > low0:
                    p.drawLine(QtCore.QPointF(t, low0), QtCore.QPointF(t, pmin))
                if high0 > pmax:
                    p.drawLine(QtCore.QPointF(t, pmax), QtCore.QPointF(t, high0))
                p.end()
                self.pictures.append(picture)
        self.low, self.high = low, high

    # 手动重画
    # ----------------------------------------------------------------------
    def update(self):
        if not self.scene() is None:
            self.scene().update()

    # 自动重画
    # ----------------------------------------------------------------------
    def paint(self, p, o, w):
        rect = o.exposedRect
        xmin, xmax = (max(0, int(rect.left())), min(len(self.pictures), int(rect.right())))
        [p.drawPicture(0, 0, pic) for pic in self.pictures[xmin:xmax]]

    # 定义边界
    # ----------------------------------------------------------------------
    def boundingRect(self):
        return QtCore.QRectF(0, self.low, len(self.pictures), (self.high - self.low))


########################################################################
class KLineWidget(KeyWraper):
    """用于显示价格走势图"""

    def onRClick(self, pos):
        super().onRClick(pos)

    # 保存K线数据的列表和Numpy Array对象
    listBar = []
    listVol = []
    listHigh = []
    listLow = []
    listSig = []
    listOpenInterest = []
    arrows = []

    # 是否完成了历史数据的读取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, data=None, graph_type=GRAPH_TYPE_KBAR):
        """Constructor"""
        self.parent = parent
        super(KLineWidget, self).__init__(parent)

        # 当前序号
        self.x_center = 0  # 下标
        self.visible_countK = 60  # 显示的Ｋ线范围
        self.main_graph_type = graph_type

        # 缓存数据
        self.data = []
        self.fields_name = None  # data 的字段名称
        self.listBar = []
        self.listVol = []
        self.listHigh = []
        self.listLow = []
        self.listSig = []
        self.listOpenInterest = []
        self.arrows = []

        self.indi_dic = {}  # 当前技术指标字典：key:指标名称，value：指标值序列

        # 所有K线上信号图
        self.allColor = deque(['blue', 'green', 'yellow', 'white', 'red'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 初始化完成
        self.initCompleted = False

        self.popup_menu = None

        # 调用函数
        self.initUi()
        if data is not None and not data.empty:
            self.convert_dataframe_local(data)

    # ----------------------------------------------------------------------
    #  初始化相关
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""

        self.grid = QtWidgets.QGridLayout()
        self.setWindowTitle(u'K线工具')
        # 主图
        self.pw = pg.PlotWidget()
        # 界面布局
        self.lay_KL = pg.GraphicsLayout(border=(100, 100, 100))
        self.lay_KL.setContentsMargins(10, 10, 10, 10)
        self.lay_KL.setSpacing(0)
        self.lay_KL.setBorder(color=(255, 255, 255, 255), width=0.8)
        self.lay_KL.setZValue(0)

        self.KLtitle = self.lay_KL.addLabel('k-bar')
        self.pw.setCentralItem(self.lay_KL)
        # 设置横坐标
        xdict = {}
        self.axisTime = StringAxisX(xdict, orientation='bottom')

        # 初始化子图
        self.initplotKline()
        self.initplotVol()
        self.initplotOI()
        self.init_plot_indi()
        # 注册十字光标
        self.crosshair = Crosshair(self.pw, self)
        # 设置界面

        self.grid.addWidget(self.pw)
        self.setLayout(self.grid)
        # self.setLayout(self.vb)
        #
        self.init_popup_menu()

        # 初始化完成
        self.initCompleted = True

    def init_popup_menu(self):
        self.popup_menu = QtWidgets.QMenu(self)
        # 周期选择
        select_period_act = QtGui.QAction('周期选择', self)
        select_period_act.triggered.connect(self.select_period_act_triggered)
        self.popup_menu.addAction(select_period_act)
        #
        insert_indicator_act = QtGui.QAction('插入指标', self)
        insert_indicator_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(insert_indicator_act)

    def select_period_act_triggered(self):
        """周期选择"""

    def insert_indicator_act_triggered(self):
        """"""
        print(QtGui.QCursor.pos())

    def contextMenuEvent(self, event):
        """右键点击事件"""

        self.popup_menu.popup(QtGui.QCursor.pos())

    def on_data_changed(self, event):
        """当数据发生改变时发生"""

    def makePI(self, name):
        """生成PlotItem对象"""
        vb = CustomViewBox()
        plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axisTime})
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()
        return plotItem

    # ----------------------------------------------------------------------
    def initplotVol(self):
        """初始化成交量子图"""
        self.pwVol = self.makePI('PlotVol')
        self.volume = CandlestickItem(self.listVol)
        self.pwVol.addItem(self.volume)
        self.pwVol.setMaximumHeight(150)
        self.pwVol.setXLink('PlotOI')
        # self.pwVol.hideAxis('bottom')

        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwVol)

    # ----------------------------------------------------------------------
    def initplotKline(self):
        """初始化K线子图"""
        self.pwKL = self.makePI('PlotKL')
        self.candle = CandlestickItem(self.listBar)
        self.pwKL.addItem(self.candle)
        self.pwKL.setXLink('PlotOI')
        # self.pwKL.hideAxis('bottom')

        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pwKL)

    # ----------------------------------------------------------------------
    def initplotOI(self):
        """初始化持仓量子图"""
        self.pwOI = self.makePI('PlotOI')
        self.curveOI = self.pwOI.plot()

        # self.lay_KL.nextRow()
        # self.lay_KL.addItem(self.pwOI)

    def init_plot_indi(self):
        """初始化技术指标子图"""
        self.pw_ta = self.makePI('plot_indi')
        self.curve_ta = pg.PlotDataItem()
        self.pw_ta.addItem(self.curve_ta)
        self.pw_ta.setXLink('PlotKL')
        # self.pw_ta.hideAxis('bottom')
        self.lay_KL.nextRow()
        self.lay_KL.addItem(self.pw_ta)

    # ----------------------------------------------------------------------
    #  画图相关
    # ----------------------------------------------------------------------
    def plotVol(self, redraw=False, xmin=0, xmax=-1):
        """重画成交量子图"""
        if self.initCompleted:
            self.volume.generatePicture(self.listVol[xmin:xmax], redraw)  # 画成交量子图

    # ----------------------------------------------------------------------
    def plotKline(self, redraw=False, xmin=0, xmax=-1):
        """重画K线子图"""
        if self.initCompleted:
            self.candle.generatePicture(self.listBar[xmin:xmax], redraw)  # 画K线
            self.plotMark()  # 显示开平仓信号位置
            # print(self.listHigh[xmin:xmax])

            # 测试加入指标
            self.curveOI1 = pg.PlotDataItem()
            self.curveOI1.setData(y=self.listHigh[xmin:xmax])
            # self.pw_ta.addItem(self.curveOI1)
            # self.pw_ta.replot()
            self.pwKL.addItem(self.curveOI1)

    # ----------------------------------------------------------------------
    def plotOI(self, xmin=0, xmax=-1):
        """重画持仓量子图"""
        if self.initCompleted:
            self.curveOI.setData(self.listOpenInterest[xmin:xmax] + [0], name="OpenInterest")  ##symbol='o',

    def plot_indi(self, redraw=False, xmin=0, xmax=-1):
        """重画技术指标子图"""
        if self.initCompleted:
            self.curve_ta.setData(y=self.listHigh[xmin:xmax] + [0], name="TA")  ##symbol='o',

            # self.pw_ta.plot()

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信号图"""
        if sig in self.sigPlots:
            self.pwKL.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pwKL.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信号图"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]
        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """显示开平仓信号"""
        # 检查是否有数据
        if len(self.data) == 0:
            return
        for arrow in self.arrows:
            self.pwKL.removeItem(arrow)
        # 画买卖信号
        for i in range(len(self.listSig)):
            # 无信号
            if self.listSig[i] == 0:
                continue
            # 买信号
            elif self.listSig[i] > 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['low']), angle=90, brush=(255, 0, 0))

            # 卖信号
            elif self.listSig[i] < 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['high']), angle=-90, brush=(0, 255, 0))
            self.pwKL.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    def updateAll(self):
        """
        手动更新所有K线图形，K线播放模式下需要
        """
        datas = self.data
        self.volume.update()
        self.candle.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pwKL.getViewBox(), 'low', 'high')
        update(self.pwVol.getViewBox(), 'volume', 'volume')

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        xMax = len(self.data) if xMax < 0 else xMax
        self.visible_countK = xMax - xMin
        self.x_center = int((xMax + xMin) / 2)
        self.pwOI.setLimits(xMin=xMin, xMax=xMax)
        self.pwKL.setLimits(xMin=xMin, xMax=xMax)
        self.pwVol.setLimits(xMin=xMin, xMax=xMax)
        self.pw_ta.setLimits(xMin=xMin, xMax=xMax)

        self.plotKline(redraw, xMin, xMax)  # K线图
        self.plotVol(redraw, xMin, xMax)  # K线副图，成交量
        self.plot_indi(redraw, xMin, xMax)  # K线副图，成交量
        # self.plotOI(0, len(self.data))  # K线副图，持仓量
        self.refresh()

    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三个子图的现实范围
        """
        datas = self.data
        minutes = int(self.visible_countK / 2)
        xmin = max(0, self.x_center - minutes)
        xmax = xmin + 2 * minutes
        self.pwOI.setRange(xRange=(xmin, xmax))
        self.pwKL.setRange(xRange=(xmin, xmax))
        self.pwVol.setRange(xRange=(xmin, xmax))
        self.pw_ta.setRange(xRange=(xmin, xmax))

    # ----------------------------------------------------------------------
    #  快捷键相关
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳转到下一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            datalen = len(self.listSig)
            self.x_center += 1
            while self.x_center < datalen and self.listSig[self.x_center] == 0:
                self.x_center += 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳转到上一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            self.x_center -= 1
            while self.x_center > 0 and self.listSig[self.x_center] == 0:
                self.x_center -= 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大显示区间"""
        self.visible_countK = min(len(self.data), int(self.visible_countK * 1.2) + 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_countK / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_countK / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_countK / 2 - 2 if x > self.x_center + self.visible_countK / 2 - 2 else x
            y = self.data[x][2]
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onUp(self):
        """缩小显示区间"""
        self.visible_countK = max(3, int(self.visible_countK / 1.2) - 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_countK / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_countK / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_countK / 2 - 2 if x > self.x_center + self.visible_countK / 2 - 2 else x
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) > 2:
            x = int(self.crosshair.xAxis) - 1
            y = self.data[x]['close']
            if x <= self.x_center - self.visible_countK / 2 + 2 and self.x_center > 1:
                self.x_center -= 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) < len(self.data) - 1:
            x = int(self.crosshair.xAxis) + 1
            y = self.data[x]['close']
            if x >= self.x_center + int(self.visible_countK / 2) - 2:
                self.x_center += 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    # 界面回调相关
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回调"""
        view = self.pwKL.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        self.x_center = int((xmin + xmax) / 2) + 1

    def onLDoubleClick(self, pos):
        if self.crosshair.visible:
            self.crosshair.hide()
        else:
            self.crosshair.show()
            self.crosshair.plotInfo(pos.x())

    # ----------------------------------------------------------------------
    def resignData(self, data):
        """更新数据，用于Y坐标自适应"""
        self.crosshair.data = data

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(data))
            if len(data) > 0 and xmax > xmin:
                ymin = min(data[xmin:xmax][low])
                ymax = max(data[xmin:xmax][high])
                self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pwKL.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'low', 'high'))

        view = self.pwVol.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'volume', 'volume'))

        # view = self.pwOI.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 数据相关
    # ----------------------------------------------------------------------
    def clearData(self):
        """清空数据"""
        # 清空数据，重新画图
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.data = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新买卖信号"""
        self.listSig = sig
        self.plotMark()

    # ----------------------------------------------------------------------
    def onBar(self, bar, nWindow=20):
        """
        新增K线数据,K线播放模式
        nWindow : 最大数据窗口
        """
        # 是否需要更新K线
        newBar = False if len(self.data) > 0 and bar.datetime == self.data[-1].datetime else True
        nrecords = len(self.data) if newBar else len(self.data) - 1
        bar.openInterest = np.random.randint(0,
                                             3) if bar.openInterest == np.inf or bar.openInterest == -np.inf else bar.openInterest
        recordVol = (nrecords, bar.volume, 0, 0, bar.volume) if bar.close_price < bar.open_price else (
            nrecords, 0, bar.volume, 0, bar.volume)
        if newBar and any(self.data):
            self.data.resize(nrecords + 1, refcheck=0)
            self.listBar.resize(nrecords + 1, refcheck=0)
            self.listVol.resize(nrecords + 1, refcheck=0)
        elif any(self.data):
            self.listLow.pop()
            self.listHigh.pop()
            self.listOpenInterest.pop()
        if any(self.data):
            self.data[-1] = (bar.datetime, bar.open_price, bar.close_price, bar.low_price, bar.high_price, bar.volume, bar.openInterest)
            self.listBar[-1] = (nrecords, bar.open_price, bar.close_price, bar.low_price, bar.high_price)
            self.listVol[-1] = recordVol
        else:
            self.data = np.rec.array(
                [(datetime, bar.open_price, bar.close_price, bar.low_price, bar.high_price, bar.volume, bar.openInterest)], \
                names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
            self.listBar = np.rec.array([(nrecords, bar.open_price, bar.close_price, bar.low_price, bar.high_price)], \
                                        names=('datetime', 'open', 'close', 'low', 'high'))
            self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
            self.resignData(self.data)
        self.axisTime.update_xdict({nrecords: bar.datetime})
        self.listLow.append(bar.low_price)
        self.listHigh.append(bar.high_price)
        self.listOpenInterest.append(bar.openInterest)
        xMax = nrecords + 1
        xMin = max(0, nrecords - nWindow)
        if not newBar:
            self.updateAll()
        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    # ----------------------------------------------------------------------
    def convert_dataframe_local(self, df=pd.DataFrame):
        """
        将pandas.DataFrame 数据格式转变为做图可用的
        datas : DataFrame 数据格式，cols :  open, close, low, high, volume, index: datetime, ''yyyyMddhhssmm
        """
        # 设置中心点时间
        self.x_center = 0

        # 绑定数据，更新横坐标映射，更新Y轴自适应函数，更新十字光标映射
        # print(df.head())
        df['time_int'] = np.array(range(len(df.index)))
        # self.data = df[['open', 'close', 'low', 'high', 'volume']].to_records()  # , 'openInterest'
        self.data = df[list(df.columns)].to_records(False)
        self.fields_name = list(df.columns)
        self.axisTime.xdict = {}
        xdict = dict(enumerate(df.index.tolist()))
        self.axisTime.update_xdict(xdict)
        self.resignData(self.data)
        # 更新画图用到的数据
        self.listBar = df[['time_int', 'open', 'close', 'low', 'high']].to_records(False)

        self.listHigh = list(df['high'])
        self.listLow = list(df['low'])
        # self.listOpenInterest = list(datas['openInterest'])
        # 成交量颜色和涨跌同步，K线方向由涨跌决定
        datas0 = pd.DataFrame()
        datas0['open'] = df.apply(lambda x: 0 if x['close'] >= x['open'] else x['volume'], axis=1)
        datas0['close'] = df.apply(lambda x: 0 if x['close'] < x['open'] else x['volume'], axis=1)
        datas0['low'] = datas0['open']
        datas0['high'] = datas0['close']
        datas0['time_int'] = np.array(range(len(df.index)))
        self.listVol = datas0[['time_int', 'open', 'close', 'low', 'high']].to_records(False)
        # 调用画图函数
        self.plotAll(True, 0, len(self.data))


class GraphWidget(KeyWraper):
    """用于显示各类图"""

    def onRClick(self, pos):
        super().onRClick(pos)

    # 是否完成了历史数据的读取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, data=None, fields_name=None,
                 graph_type=GRAPH_TYPE_LINE, graph_count=1,
                 data_sgn='data', low_sgn=None, high_sgn=None):
        """Constructor"""
        self.parent = parent
        super(GraphWidget, self).__init__(parent)
        self.main_graph_type = graph_type
        self.data_sgn = data_sgn
        self.low_sgn = low_sgn if low_sgn else data_sgn
        self.high_sgn = high_sgn if high_sgn else data_sgn

        # 当前序号
        self.x_center = 0  # 下标中间
        self.visible_count = 60  # 显示的数据长度
        self.graph_count = graph_count  # 图的数量
        self.other_graph = []  # 主图外的其它图列表

        # 缓存数据
        self.data = []
        self.fields_name = fields_name  # data 的字段名称

        # 所有信号图
        self.allColor = deque(['blue', 'green', 'yellow', 'white', 'red'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 初始化完成
        self.initCompleted = False

        self.popup_menu = None

        # 调用函数
        self.initUi()
        time.sleep(1)
        if data is not None and not data.empty:
            self.convert_dataframe_local(data)

    # ----------------------------------------------------------------------
    #  初始化相关
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.initCompleted = False
        self.grid = QtWidgets.QGridLayout()
        self.setWindowTitle('')
        # 主图
        self.pg_main_plot_widget = pg.PlotWidget()
        # 界面布局
        self.main_layout = pg.GraphicsLayout(border=(100, 100, 100))
        self.main_layout.setContentsMargins(10, 10, 10, 10)
        self.main_layout.setSpacing(0)
        self.main_layout.setBorder(color=(255, 255, 255, 255), width=0.8)
        self.main_layout.setZValue(0)

        self.main_title = self.main_layout.addLabel('')
        self.pg_main_plot_widget.setCentralItem(self.main_layout)
        # 设置横坐标
        xdict = {}
        self.axis_x = StringAxisX(xdict, orientation='bottom')

        # 初始化主图
        self.init_main_plot()
        # 注册十字光标
        self.crosshair = Crosshair(self.pg_main_plot_widget, self, sync_num=self.graph_count)
        # 设置界面

        self.grid.addWidget(self.pg_main_plot_widget)
        self.setLayout(self.grid)
        # self.setLayout(self.vb)
        #
        self.init_popup_menu()

        # 初始化完成

        self.initCompleted = True

    def init_popup_menu(self):
        self.popup_menu = QtWidgets.QMenu(self)
        # 周期选择
        select_period_act = QtGui.QAction('周期选择', self)
        select_period_act.triggered.connect(self.select_period_act_triggered)
        self.popup_menu.addAction(select_period_act)
        #
        insert_indicator_act = QtGui.QAction('插入指标', self)
        insert_indicator_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(insert_indicator_act)

    def select_period_act_triggered(self):
        """周期选择"""

    def insert_indicator_act_triggered(self):
        """"""
        print(QtGui.QCursor.pos())

    def contextMenuEvent(self, event):
        """右键点击事件"""

        self.popup_menu.popup(QtGui.QCursor.pos())

    def on_data_changed(self, event):
        """当数据发生改变时发生"""

    def make_plot_item(self, name):
        """生成PlotItem对象"""
        vb = CustomViewBox()
        plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axis_x})
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()
        return plotItem

    # ----------------------------------------------------------------------
    def init_main_plot(self):
        """初始化主图"""
        self.pw_main_item = self.make_plot_item('PlotMain')
        if self.main_graph_type != GRAPH_TYPE_KBAR:
            self.main_curve = self.pw_main_item.plot()  # CandlestickItem(self.data)
            self.pw_main_item.addItem(self.main_curve)
        # self.pw_main_item.hideAxis('bottom')

        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_main_item)

    # ----------------------------------------------------------------------
    def initplotOI(self):
        """初始化持仓量子图"""
        self.pwOI = self.make_plot_item('PlotOI')
        self.curveOI = self.pwOI.plot()

        # self.lay_KL.nextRow()
        # self.lay_KL.addItem(self.pwOI)

    def init_plot_indi(self):
        """初始化技术指标子图"""
        self.pw_ta = self.make_plot_item('plot_indi')
        self.curve_ta = self.pw_ta.plot()
        self.pw_ta.setXLink('PlotKL')
        # self.pw_ta.hideAxis('bottom')
        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_ta)

    # ----------------------------------------------------------------------
    #  画图相关
    # ----------------------------------------------------------------------
    def plot_bar(self, redraw=False, xmin=0, xmax=-1):
        """重画柱状图"""
        if self.initCompleted:
            self.volume.generatePicture(self.listVol[xmin:xmax], redraw)

    # ----------------------------------------------------------------------
    def plot_main(self, redraw=False, xmin=0, xmax=-1):
        """重画折线图"""
        if self.initCompleted:
            # self.main_curve = pg.PlotDataItem()
            self.main_curve.setData(y=self.data[xmin:xmax][self.data_sgn])
            # self.main_curve.addItem(self.curveOI1)
            # self.main_curve.generatePicture(self.listBar[xmin:xmax], redraw)  # 画 线

    def add_line(self, data, field_name='', graph_area_no=0):
        """增加折线"""

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信号图"""
        if sig in self.sigPlots:
            self.pw_main_item.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pw_main_item.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信号图"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]
        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """显示开平仓信号"""
        # 检查是否有数据
        if len(self.data) == 0:
            return
        for arrow in self.arrows:
            self.pw_main_item.removeItem(arrow)
        # 画买卖信号
        for i in range(len(self.listSig)):
            # 无信号
            if self.listSig[i] == 0:
                continue
            # 买信号
            elif self.listSig[i] > 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['low']), angle=90, brush=(255, 0, 0))

            # 卖信号
            elif self.listSig[i] < 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['high']), angle=-90, brush=(0, 255, 0))
            self.pw_main_item.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    def updateAll(self):
        """
        手动更新所有K线图形，K线播放模式下需要
        """
        datas = self.data
        self.volume.update()
        self.main_curve.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pw_main_item.getViewBox(), self.low_sgn, self.high_sgn)

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        xMax = len(self.data) if xMax < 0 else xMax
        self.visible_count = xMax - xMin
        self.x_center = int((xMax + xMin) / 2)
        self.pw_main_item.setLimits(xMin=xMin, xMax=xMax)
        self.plot_main(redraw, xMin, xMax)
        self.refresh()

    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三个子图的现实范围
        """
        data = self.data
        minutes = int(self.visible_count / 2)
        xmin = max(0, self.x_center - minutes)
        xmax = xmin + 2 * minutes

        self.pw_main_item.setRange(xRange=(xmin, xmax))

    # ----------------------------------------------------------------------
    #  快捷键相关
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳转到下一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            datalen = len(self.listSig)
            self.x_center += 1
            while self.x_center < datalen and self.listSig[self.x_center] == 0:
                self.x_center += 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳转到上一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            self.x_center -= 1
            while self.x_center > 0 and self.listSig[self.x_center] == 0:
                self.x_center -= 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大显示区间"""
        self.visible_count = min(len(self.data), int(self.visible_count * 1.2) + 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_count / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
            y = self.data[x][2]
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onUp(self):
        """缩小显示区间"""
        self.visible_count = max(3, int(self.visible_count / 1.2) - 1)
        self.refresh()
        if len(self.data) > 0:
            x = self.x_center - self.visible_count / 2 + 2 if int(
                self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
            x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
            y = self.data[x]['close']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) > 2:
            x = int(self.crosshair.xAxis) - 1
            y = self.data[x]['close']
            if x <= self.x_center - self.visible_count / 2 + 2 and self.x_center > 1:
                self.x_center -= 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) < len(self.data) - 1:
            x = int(self.crosshair.xAxis) + 1
            y = self.data[x]['close']
            if x >= self.x_center + int(self.visible_count / 2) - 2:
                self.x_center += 1
                self.refresh()
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    # 界面回调相关
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回调"""
        view = self.pw_main_item.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        self.x_center = int((xmin + xmax) / 2) + 1

    def onLDoubleClick(self, pos):
        if self.crosshair.visible:
            self.crosshair.hide()
        else:
            self.crosshair.show()
            self.crosshair.plotDataInfo(pos.x())

    # ----------------------------------------------------------------------
    def resignData(self, data):
        """更新数据，用于Y坐标自适应"""
        self.crosshair.data = data

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(data))
            if len(data) > 0 and xmax > xmin:
                ymin = min(data[xmin:xmax][low])
                ymax = max(data[xmin:xmax][high])
                self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pw_main_item.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, self.low_sgn, self.high_sgn))

        # view = self.pwVol.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'volume', 'volume'))

        # view = self.pwOI.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 数据相关
    # ----------------------------------------------------------------------
    def clearData(self):
        """清空数据"""
        # 清空数据，重新画图
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.data = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新买卖信号"""
        self.listSig = sig
        self.plotMark()

    # ----------------------------------------------------------------------
    def on_data_update(self, data, nWindow=20):
        """
        新增数据
        nWindow : 最大数据窗口
        """
        # 是否需要更新
        xMin = 1
        xMax = 100
        self.updateAll()
        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    # ----------------------------------------------------------------------
    def convert_dataframe_local(self, df):
        """
        将pandas.DataFrame数据格式转变为做图可用的
        datas : 数据格式，cols : datetime, open, close, low, high
        """
        # 设置中心点时间
        self.x_center = 0
        self.axis_x.xdict = {}
        xdict = dict(enumerate(df.index.tolist()))
        # 绑定数据，更新横坐标映射，更新Y轴自适应函数，更新十字光标映射
        if isinstance(df, pd.DataFrame):
            df['time_int'] = np.array(range(len(df.index)))
            self.data = df[list(df.columns)].to_records(False)
            self.fields_name = list(df.columns)
        else:
            time_int = np.array(range(len(df.index)))
            data = pd.DataFrame.from_dict(data={self.data_sgn: list(df), 'time_int': time_int, 'datetime': list(df.index)})
            self.data = data[list(data.columns)].to_records(False)
            self.fields_name = list(data.columns)

        self.axis_x.update_xdict(xdict)
        self.resignData(self.data)

        # 调用画图函数
        self.plotAll(True, 0, len(self.data))


class TickWidget(KeyWraper):
    """用于Tick线图"""

    def onRClick(self, pos):
        super().onRClick(pos)

    # 是否完成了历史数据的读取
    initCompleted = False

    # ----------------------------------------------------------------------
    def __init__(self, parent=None, data=None, fields_name=None, graph_type=GRAPH_TYPE_LINE):
        """Constructor"""
        self.parent = parent
        super(TickWidget, self).__init__(parent)
        self.main_graph_type = graph_type

        # 当前序号
        self.x_center = 0  # 下标中间
        self.visible_count = MAX_TICKS  # 显示的数据长度
        self.graph_count = 1  # 图的数量
        self.other_graph = []  # 主图外的其它图列表

        # 缓存数据
        self.data = []
        self.data_len = 2 * 60 * 60 * 95  # 每秒2个Tick*60秒*60分*9.5小时（白天4小时+晚上5.5小时）
        self.fields_name = fields_name  # data 的字段名称

        self.indi_dic = {}  # 当前技术指标字典：key:指标名称，value：指标值序列

        # 所有信号图
        self.allColor = deque(['blue', 'green', 'yellow', 'white', 'red'])
        self.sigData = {}
        self.sigColor = {}
        self.sigPlots = {}

        # 初始化完成
        self.initCompleted = False

        self.popup_menu = None

        # 调用函数
        self.initUi()
        time.sleep(1)
        if data is not None and not data.empty:
            self.convert_dataframe_local(data)

    # ----------------------------------------------------------------------
    #  初始化相关
    # ----------------------------------------------------------------------
    def initUi(self):
        """初始化界面"""
        self.initCompleted = False
        self.grid = QtWidgets.QGridLayout()
        self.setWindowTitle('')
        # 主图
        self.pg_main_plot_widget = pg.PlotWidget()
        # 界面布局
        self.main_layout = pg.GraphicsLayout(border=(100, 100, 100))
        self.main_layout.setContentsMargins(10, 10, 10, 10)
        self.main_layout.setSpacing(0)
        self.main_layout.setBorder(color=(255, 255, 255, 255), width=0.8)
        self.main_layout.setZValue(0)

        self.main_title = self.main_layout.addLabel('')
        self.pg_main_plot_widget.setCentralItem(self.main_layout)
        # 设置横坐标
        xdict = {}
        self.axis_x = StringAxisX(xdict, orientation='bottom')

        # 初始化主图
        self.init_main_plot()

        # 注册十字光标
        self.crosshair = Crosshair(self.pg_main_plot_widget, self, sync_num=2)
        # 设置界面

        self.grid.addWidget(self.pg_main_plot_widget)
        self.setLayout(self.grid)
        # self.setLayout(self.vb)
        #
        self.init_popup_menu()

        # 初始化完成

        self.initCompleted = True

    def init_popup_menu(self):
        self.popup_menu = QtWidgets.QMenu(self)
        # 周期选择
        select_period_act = QtGui.QAction('周期选择', self)
        select_period_act.triggered.connect(self.select_period_act_triggered)
        self.popup_menu.addAction(select_period_act)
        #
        insert_indicator_act = QtGui.QAction('插入指标', self)
        insert_indicator_act.triggered.connect(self.insert_indicator_act_triggered)
        self.popup_menu.addAction(insert_indicator_act)

    def select_period_act_triggered(self):
        """周期选择"""

    def insert_indicator_act_triggered(self):
        """"""
        print(QtGui.QCursor.pos())

    def contextMenuEvent(self, event):
        """右键点击事件"""

        self.popup_menu.popup(QtGui.QCursor.pos())

    def on_data_changed(self, event):
        """当数据发生改变时发生"""

    def make_plot_item(self, name):
        """生成PlotItem对象"""
        vb = CustomViewBox()
        plotItem = pg.PlotItem(viewBox=vb, name=name, axisItems={'bottom': self.axis_x})
        plotItem.setMenuEnabled(False)
        plotItem.setClipToView(True)
        plotItem.hideAxis('left')
        plotItem.showAxis('right')
        plotItem.setDownsampling(mode='peak')
        plotItem.setRange(xRange=(0, 1), yRange=(0, 1))
        plotItem.getAxis('right').setWidth(60)
        plotItem.getAxis('right').setStyle(tickFont=QFont("Roman times", 10, QFont.Bold))
        plotItem.getAxis('right').setPen(color=(255, 255, 255, 255), width=0.8)
        plotItem.showGrid(True, True)
        plotItem.hideButtons()
        return plotItem

    # ----------------------------------------------------------------------
    def init_main_plot(self):
        """初始化主图"""
        self.pw_main_item = self.make_plot_item('PlotMain')
        # self.pw_main_item = self.main_layout.addPlot()

        self.main_curve = self.pw_main_item.plot()  # CandlestickItem(self.data)
        self.pw_main_item.addItem(self.main_curve)

        # self.pw_main_item.hideAxis('bottom')

        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_main_item)
        # 初始化成交量子图
        self.init_vol_plot()

    def init_vol_plot(self):
        """初始化成交量子图"""
        self.pw_vol = self.make_plot_item('PlotVol')
        # self.pw_vol = self.main_layout.addPlot()
        self.volume_bar = self.pw_vol.plot()
        self.pw_vol.addItem(self.volume_bar)
        self.pw_vol.setMaximumHeight(150)
        self.pw_vol.hideAxis('bottom')
        self.pw_vol.setXLink('PlotMain')

        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_vol)

    def init_plot_indi(self):
        """初始化技术指标子图"""
        self.pw_ta = self.make_plot_item('plot_indi')
        self.curve_ta = self.pw_ta.plot()
        self.pw_ta.setXLink('PlotKL')
        # self.pw_ta.hideAxis('bottom')
        self.main_layout.nextRow()
        self.main_layout.addItem(self.pw_ta)

    # ----------------------------------------------------------------------
    #  画图相关
    # ----------------------------------------------------------------------
    def plot_vol(self, redraw=False, xmin=0, xmax=-1):
        """重画柱状图"""
        if self.initCompleted:
            # print(self.listVol[xmin:xmax])
            self.volume_bar.setData(y=self.listVol[xmin:xmax])
            self.pw_vol.replot()

    # ----------------------------------------------------------------------
    def plot_main(self, redraw=False, xmin=0, xmax=-1):
        """重画折线图"""
        if self.initCompleted:
            # self.main_curve = pg.PlotDataItem()
            self.main_curve.setData(y=self.listTick[xmin:xmax])
            # self.main_curve.addItem(self.curveOI1)
            # self.main_curve.generatePicture(self.listBar[xmin:xmax], redraw)  # 画 线

    def add_line(self, data, field_name='', graph_area_no=0):
        """增加折线"""

    # ----------------------------------------------------------------------
    def addSig(self, sig):
        """新增信号图"""
        if sig in self.sigPlots:
            self.pw_main_item.removeItem(self.sigPlots[sig])
        self.sigPlots[sig] = self.pw_main_item.plot()
        self.sigColor[sig] = self.allColor[0]
        self.allColor.append(self.allColor.popleft())

    # ----------------------------------------------------------------------
    def showSig(self, datas):
        """刷新信号图"""
        for sig in self.sigPlots:
            self.sigData[sig] = datas[sig]
        [self.sigPlots[sig].setData(datas[sig], pen=self.sigColor[sig][0], name=sig) \
         for sig in self.sigPlots]  # if sig in datas]

    # ----------------------------------------------------------------------
    def plotMark(self):
        """显示开平仓信号"""
        # 检查是否有数据
        if len(self.data) == 0:
            return
        for arrow in self.arrows:
            self.pw_main_item.removeItem(arrow)
        # 画买卖信号
        for i in range(len(self.listSig)):
            # 无信号
            if self.listSig[i] == 0:
                continue
            # 买信号
            elif self.listSig[i] > 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['lastPrice']), angle=90, brush=(255, 0, 0))

            # 卖信号
            elif self.listSig[i] < 0:
                arrow = pg.ArrowItem(pos=(i, self.data[i]['high']), angle=-90, brush=(0, 255, 0))
            self.pw_main_item.addItem(arrow)
            self.arrows.append(arrow)

    # ----------------------------------------------------------------------
    def updateAll(self):
        """
        手动更新所有K线图形，K线播放模式下需要
        """
        datas = self.data
        self.volume_bar.update()
        self.main_curve.update()

        def update(view, low, high):
            vRange = view.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(datas))
            if len(datas) > 0 and xmax > xmin:
                ymin = min(datas[xmin:xmax][low])
                ymax = max(datas[xmin:xmax][high])
                view.setRange(yRange=(ymin, ymax))
            else:
                view.setRange(yRange=(0, 1))

        update(self.pw_main_item.getViewBox(), 'lastPrice', 'lastPrice')
        update(self.pw_vol.getViewBox(), 'volume', 'volume')

    # ----------------------------------------------------------------------
    def plotAll(self, redraw=True, xMin=0, xMax=-1):
        """
        重画所有界面
        redraw ：False=重画最后一根K线; True=重画所有
        xMin,xMax : 数据范围
        """
        xMax = len(self.data) if xMax < 0 else xMax
        self.visible_count = xMax - xMin
        self.x_center = int((xMax + xMin) / 2)
        self.pw_main_item.setLimits(xMin=xMin, xMax=xMax)
        self.plot_main(redraw, xMin, xMax)
        self.plot_vol(redraw, xMin, xMax)
        self.refresh()

    # ----------------------------------------------------------------------
    def refresh(self):
        """
        刷新三个子图的现实范围
        """
        data = self.data
        minutes = int(self.visible_count / 2)
        xmin = max(0, self.x_center - minutes)
        xmax = xmin + 2 * minutes
        # print('refresh: ', xmin, xmax)

        self.pw_main_item.setRange(xRange=(xmin, xmax))

    # ----------------------------------------------------------------------
    #  快捷键相关
    # ----------------------------------------------------------------------
    def onNxt(self):
        """跳转到下一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            datalen = len(self.listSig)
            self.x_center += 1
            while self.x_center < datalen and self.listSig[self.x_center] == 0:
                self.x_center += 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['lastPrice']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onPre(self):
        """跳转到上一个开平仓点"""
        if len(self.listSig) > 0 and not self.x_center is None:
            self.x_center -= 1
            while self.x_center > 0 and self.listSig[self.x_center] == 0:
                self.x_center -= 1
            self.refresh()
            x = self.x_center
            y = self.data[x]['lastPrice']
            self.crosshair.signal.emit((x, y))

    # ----------------------------------------------------------------------
    def onDown(self):
        """放大显示区间"""
        self.visible_count = min(len(self.data), int(self.visible_count * 1.2) + 1)
        self.refresh()
        if len(self.data) > 0:
            try:
                x = self.x_center - self.visible_count / 2 + 2 if int(
                    self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
                x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
                y = self.data[x]['lastPrice']
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    def onUp(self):
        """缩小显示区间"""
        self.visible_count = max(3, int(self.visible_count / 1.2) - 1)
        self.refresh()
        if len(self.data) > 0:
            try:
                x = self.x_center - self.visible_count / 2 + 2 if int(
                    self.crosshair.xAxis) < self.x_center - self.visible_count / 2 + 2 else int(self.crosshair.xAxis)
                x = self.x_center + self.visible_count / 2 - 2 if x > self.x_center + self.visible_count / 2 - 2 else x
                # print('onUp:x=', x)
                y = self.data[x]['lastPrice']
                # print('onUp:x,y=',x,y)
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    def onLeft(self):
        """向左移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) > 2:
            try:
                x = int(self.crosshair.xAxis) - 1
                y = self.data[x]['lastPrice']
                if x <= self.x_center - self.visible_count / 2 + 2 and self.x_center > 1:
                    self.x_center -= 1
                    self.refresh()
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    def onRight(self):
        """向右移动"""
        if len(self.data) > 0 and int(self.crosshair.xAxis) < len(self.data) - 1:
            try:
                x = int(self.crosshair.xAxis) + 1
                y = self.data[x]['lastPrice']
                if x >= self.x_center + int(self.visible_count / 2) - 2:
                    self.x_center += 1
                    self.refresh()
                self.crosshair.signal.emit((x, y))
            except:
                pass

    # ----------------------------------------------------------------------
    # 界面回调相关
    # ----------------------------------------------------------------------
    def onPaint(self):
        """界面刷新回调"""
        view = self.pw_main_item.getViewBox()
        vRange = view.viewRange()
        xmin = max(0, int(vRange[0][0]))
        xmax = max(0, int(vRange[0][1]))
        self.x_center = int((xmin + xmax) / 2) + 1

    def onLDoubleClick(self, pos):
        if self.crosshair.visible:
            self.crosshair.hide()
        else:
            self.crosshair.show()
            self.crosshair.plotDataInfo(pos.x())

    # ----------------------------------------------------------------------
    def resignData(self, data):
        """更新数据，用于Y坐标自适应"""
        self.crosshair.data = data

        def viewXRangeChanged(low, high, self):
            vRange = self.viewRange()
            xmin = max(0, int(vRange[0][0]))
            xmax = max(0, int(vRange[0][1]))
            xmax = min(xmax, len(data))
            if len(data) > 0 and xmax > xmin:
                ymin = min(data[xmin:xmax][low])
                ymax = max(data[xmin:xmax][high])
                self.setRange(yRange=(ymin, ymax))
            else:
                self.setRange(yRange=(0, 1))

        view = self.pw_main_item.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'LastPrice', 'LastPrice'))

        view = self.pw_vol.getViewBox()
        view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'Volume', 'Volume'))

        # view = self.pwOI.getViewBox()
        # view.sigXRangeChanged.connect(partial(viewXRangeChanged, 'openInterest', 'openInterest'))

    # ----------------------------------------------------------------------
    # 数据相关
    # ----------------------------------------------------------------------
    def clear_data(self):
        """清空数据"""
        # 清空数据，重新画图
        self.time_index = []
        self.listBar = []
        self.listVol = []
        self.listLow = []
        self.listHigh = []
        self.listOpenInterest = []
        self.listSig = []
        self.sigData = {}
        self.arrows = []
        self.data = None

    # ----------------------------------------------------------------------
    def updateSig(self, sig):
        """刷新买卖信号"""
        self.listSig = sig
        self.plotMark()

    # ----------------------------------------------------------------------
    def on_tick(self, tick, nWindow=20):
        """
        新增Tick数据
        nWindow : 最大数据窗口
        """
        # 是否需要更新K线
        newBar = False if len(self.data) > 0 and tick.datetime == self.data[-1].datetime else True
        nrecords = len(self.data) if newBar else len(self.data) - 1
        tick.openInterest = np.random.randint(0,
                                              3) if tick.openInterest == np.inf or tick.openInterest == -np.inf else tick.openInterest
        recordVol = (nrecords, tick.volume, 0, 0, tick.volume) if tick.close_price < tick.open_price else (
            nrecords, 0, tick.volume, 0, tick.volume)
        if newBar and any(self.data):
            self.data.resize(nrecords + 1, refcheck=0)
            # self.listBar.resize(nrecords + 1, refcheck=0)
            # self.listVol.resize(nrecords + 1, refcheck=0)
        elif any(self.data):
            self.listLow.pop()
            self.listHigh.pop()
            self.listOpenInterest.pop()
        if any(self.data):
            self.data[-1] = (tick.datetime, tick.open_price, tick.close_price, tick.low_price, tick.high_price, tick.volume, tick.openInterest)
            self.listBar[-1] = (nrecords, tick.open_price, tick.close_price, tick.low_price, tick.high_price)
            self.listVol[-1] = recordVol
        else:
            self.data = np.rec.array(
                [(datetime, tick.open_price, tick.close_price, tick.low_price, tick.high_price, tick.volume, tick.openInterest)], \
                names=('datetime', 'open', 'close', 'low', 'high', 'volume', 'openInterest'))
            self.listBar = np.rec.array([(nrecords, tick.open_price, tick.close_price, tick.low_price, tick.high_price)], \
                                        names=('datetime', 'open', 'close', 'low', 'high'))
            self.listVol = np.rec.array([recordVol], names=('datetime', 'open', 'close', 'low', 'high'))
            self.resignData(self.data)
        self.axis_x.update_xdict({nrecords: tick.datetime})
        self.listLow.append(tick.low_price)
        self.listHigh.append(tick.high_price)
        self.listOpenInterest.append(tick.openInterest)
        xMax = nrecords + 1
        xMin = max(0, nrecords - nWindow)
        if not newBar:
            self.updateAll()
        self.plotAll(False, xMin, xMax)
        self.crosshair.signal.emit((None, None))

    def convert_dataframe_local(self, df):
        """
        将pandas.DataFrame数据格式转变为做图可用的
        datas : 数据格式，cols : fields = {'symbol': 1, 'datetime': 1, 'lastPrice': 1, 'volume': 1, 'askPrice1': 1,
              'askVolume1': 1, 'bidPrice1': 1, 'bidVolume1': 1, 'openInterest': 1, 'date': 1, 'time': 1,'TradingDay':1}
              数据按照日期时间由小到大排序
        """
        # 设置中心点时间
        self.x_center = 0

        # 绑定数据，更新横坐标映射，更新Y轴自适应函数，更新十字光标映射
        df['time_int'] = np.array(range(len(df.index)))
        self.data = df[list(df.columns)].to_records(False)  # , 'openInterest'
        self.fields_name = list(df.columns)
        self.listTick = list(df['LastPrice'])
        self.listVol = list(df['Volume'])
        self.axis_x.xdict = {}
        xdict = dict(enumerate(df.index.tolist()))

        self.axis_x.update_xdict(xdict)
        self.resignData(self.data)

        # 调用画图函数
        self.plotAll(True, 0, len(self.data))


########################################################################
# 功能测试
########################################################################
import sys

if __name__ == '__main__':
    app = QApplication(sys.argv)
    #
    #     path = 'E:/data/day/rb1101.txt'
    #
    #     # read Tick
    import pandas as pd
    from datetime import datetime, timedelta
    from pymongo import MongoClient
    from vnpy.api.tq.get_data import get_tick, get_bar

    # from utils.resample import ticks2bars

    port = 27017
    host = 'localhost'
    df = pd.DataFrame()
    dbClient = MongoClient(host, port, serverSelectionTimeoutMS=500)
    symbol_list = 'SHFE.rb2005'
    ed = datetime.now() + timedelta(days=1)
    sd = ed - timedelta(days=3)
    # ticks = get_tick(symbol_list, start_datetime=sd, end_datetime=ed)
    # ticks.reset_index(drop=False, inplace=True)
    # dt = ticks.iloc[0]['DateTime']
    # print(type(dt), ticks.columns)
    # ticks.set_index('DateTime', inplace=True)
    # print(ticks.head())
    # ui = TickWidget(data=ticks)

    df = get_bar(symbol_list, 60 * 5, sd, ed)
    df.rename(columns={'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume'},
              inplace=True)
    #
    # print(df.columns)
    # K线界面
    ui = KLineWidget(data=df)
    # ui = GraphWidget(data=df['close'])

    ui.show()

    app.exec_()
